#include <QCryptographicHash>
#include <QNetworkInterface>
#include <QProcess>
#include <QRegExp>
#include <QTimer>
#include <QFile>

#ifdef Q_WS_QWS
    #include <QWSServer>
    #include <QBrush>
#endif

#include "UIAccelerometer.h"
#include "UIDevice_p.h"
#include "UIDevice.h"
#include "UIEvent.h"

#define kUIDeviceKeyCodePower       (0x74)
#define kUIDeviceKeyCodeAux         (0xA9)

// ===========================================================================
// ===========================================================================
typedef struct {
    char        time[8];
    quint16     code;
    quint16     type;
    qint32      value;
} UIDeviceButtonEvent;

UIDeviceButtonThread::UIDeviceButtonThread (const QString& device, int keyCode,
                                            QObject *parent)
    : QThread(parent)
{
    m_keyCode = keyCode;
    m_device.setFileName(device);
}

UIDeviceButtonThread::~UIDeviceButtonThread() {
    if (m_device.isOpen())
        m_device.close();
}

void UIDeviceButtonThread::run (void) {
    UIDeviceButtonEvent event;
    quint64 rdsz;

    if (!m_device.open(QIODevice::ReadOnly)) {
        qDebug("UIDeviceButtonThread::open(): %s", qPrintable(m_device.errorString()));
        return;
    }

    forever {
        rdsz = m_device.read((char *)&event, sizeof(UIDeviceButtonEvent));
        if (rdsz == sizeof(UIDeviceButtonEvent) && event.code != 0x0) {
            if (event.type == m_keyCode && event.value != 0)
                emit pressed(m_keyCode);
        }
        usleep(800);
    }
}

// ===========================================================================
// ===========================================================================
class UIDevicePrivate {
    public:
        UIDeviceOrientation orientation;
        bool orientationNotifications;
        QString uniqueIdentifier;
        QString serialNumber;
        QString model;
        QString name;

    public:
        void readCpuInfo (void);
        void readSerialNumber (void);
        void calculateUniqueIdentifier (void);

        UIDeviceOrientation deviceOrientation (UIAccelerationValue x, 
                                               UIAccelerationValue y,
                                               UIAccelerationValue z) const;

        bool ledIsEnabled (const QString& fileName);
        void setLedEnabled (const QString& fileName, bool enabled);

        void setVibration (quint8 force);
};

void UIDevicePrivate::readCpuInfo (void) {
    QFile file("/proc/cpuinfo");
    if (file.open(QIODevice::ReadOnly)) {
        QByteArray cpuInfo = file.readAll();

        QRegExp regExpProcessor("Processor\\s*:\\s*([A-Z-0-9-]+)\\s+");
        if (regExpProcessor.indexIn(cpuInfo) != -1)
            model = regExpProcessor.cap(1);

        QRegExp regExpHardware("Hardware\\s*:\\s*([A-Z-0-9-]+)\\s+");
        if (regExpHardware.indexIn(cpuInfo) != -1)
            name = regExpHardware.cap(1);

        file.close();
    }
}

void UIDevicePrivate::readSerialNumber (void) {
#ifndef UIKIT_SIMULATOR
    QProcess::execute("mount", QStringList() << "-o" << "ro" << "/dev/mtdblock5" << "/mnt");    
    QFile file("/mnt/sn");
    if (file.open(QIODevice::ReadOnly)) {
        serialNumber = file.readAll().trimmed();
        file.close();
    }
    QProcess::execute("umount", QStringList() << "/mnt");
#endif
}

void UIDevicePrivate::calculateUniqueIdentifier (void) {
    QCryptographicHash hash(QCryptographicHash::Sha1);

    foreach (QNetworkInterface netIf, QNetworkInterface::allInterfaces())
        hash.addData(netIf.hardwareAddress().toLatin1());

    hash.addData(serialNumber.toLatin1());
    hash.addData(model.toLatin1());
    hash.addData(name.toLatin1());

    uniqueIdentifier = hash.result().toHex();
}

#define AccelIsNear(value, nearValue)        \
    ((nearValue) >= ((value) - (0.5f)) && (nearValue) <= ((value) + (0.5f)))
UIDeviceOrientation UIDevicePrivate::deviceOrientation (UIAccelerationValue x, 
                                                        UIAccelerationValue y,
                                                        UIAccelerationValue z) const
{
    // ( 0,  0,  1) = UIDeviceOrientationFaceUp
    // ( 0,  0, -1) = UIDeviceOrientationFaceDown
    // ( 1,  0,  0) = UIDeviceOrientationLandscapeRight
    // (-1,  0,  0) = UIDeviceOrientationLandscapeLeft
    // ( 0, -1,  0) = UIDeviceOrientationPortrait
    // ( 0,  1,  0) = UIDeviceOrientationPortraitUpsideDown
    if (AccelIsNear(x, 1) && AccelIsNear(y, 0) && AccelIsNear(z, 0))
        return(UIDeviceOrientationLandscapeRight);

    if (AccelIsNear(x, -1) && AccelIsNear(y, 0) && AccelIsNear(z, 0))
        return(UIDeviceOrientationLandscapeLeft);

    if (AccelIsNear(x, 0) && AccelIsNear(y, -1) && AccelIsNear(z, 0))
        return(UIDeviceOrientationPortrait);

    if (AccelIsNear(x, 0) && AccelIsNear(y, 1) && AccelIsNear(z, 0))
        return(UIDeviceOrientationPortraitUpsideDown);

    if (AccelIsNear(x, 0) && AccelIsNear(y, 0) && AccelIsNear(z, 1))
        return(UIDeviceOrientationFaceUp);

    if (AccelIsNear(x, 0) && AccelIsNear(y, 0) && AccelIsNear(z, -1))
        return(UIDeviceOrientationFaceDown);

    return(UIDeviceOrientationUnknown);
}

bool UIDevicePrivate::ledIsEnabled (const QString& fileName) {
    bool isEnabled = false;

    QFile file(fileName);
    if (file.open(QIODevice::ReadOnly)) {		
        isEnabled = (file.readAll().trimmed() == "1");
        file.close();
    }

    return(isEnabled);
}

void UIDevicePrivate::setLedEnabled (const QString& fileName, bool enabled) {
#ifndef UIKIT_SIMULATOR
    QFile file(fileName);
    if (file.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
        file.write(enabled ? "1" : "0");
        file.close();
    }
#else
    Q_UNUSED(fileName)
    Q_UNUSED(enabled)
#endif
}

void UIDevicePrivate::setVibration (quint8 force) {
#ifndef UIKIT_SIMULATOR
    QFile file("/sys/devices/platform/neo1973-vibrator.0/leds/neo1973:vibrator/brightness");
    if (file.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
        file.write(QByteArray::number(force));
        file.close();
    }
#else
    Q_UNUSED(force)
#endif
}

// ===========================================================================
//  PUBLIC GLOBAL STATIC Instances
// ===========================================================================
Q_GLOBAL_STATIC(UIDevice, uiDevice);

// ===========================================================================
//  PUBLIC STATIC Methods
// ===========================================================================
UIDevice *UIDevice::currentDevice (void) {
    return(uiDevice());
}

// ===========================================================================
//  PUBLIC Properties
// ===========================================================================
QString UIDevice::uniqueIdentifier (void) const {
    return(d->uniqueIdentifier);
}

// Identifying the Device and Operating System
QString UIDevice::name (void) const {
    return(d->name);
}

QString UIDevice::model (void) const {
    return(d->model);
}

QString UIDevice::systemName (void) const {
    return(UIDEVICE_SYSTEM_NAME);
}

QString UIDevice::systemVersion (void) const {
    return(UIDEVICE_SYSTEM_VERSION);
}

QString UIDevice::serialNumber (void) const {
    //mount -o ro /dev/mtdblock5 /mnt/; cat /mnt/sn; umount /mnt
    return(d->serialNumber);
}

QSize UIDevice::screenSize (void) const {
    return(orientationIsLandscape() ? QSize(640, 480) : QSize(480, 640));
}

// ===========================================================================
//  PUBLIC Properties (Orientation Related)
// ===========================================================================
bool UIDevice::orientationIsPortrait (void) const {
    return(UIDeviceOrientationIsPortrait(d->orientation));
}

bool UIDevice::orientationIsLandscape (void) const {
    return(UIDeviceOrientationIsLandscape(d->orientation));
}

UIDeviceOrientation UIDevice::orientation (void) const {
    return(d->orientation);
}

#ifdef UIKIT_SIMULATOR
void UIDevice::setOrientation (UIDeviceOrientation orientation) {
    if (d->orientation != orientation) {
        d->orientation = orientation;
        emit orientationChanged(d->orientation);
    }
}
/*#else
UIDeviceOrientation UIDevice::orientation (void) const {
    UIAccelerationValue x, y, z;

    UIAccelerometer *accelerometer = UIAccelerometer::sharedAccelerometer();
    accelerometer->acceleration(&x, &y, &z);

    return(d->deviceOrientation(x, y, z));
}*/
#endif /* UIKIT_SIMULATOR */

void UIDevice::endGeneratingDeviceOrientationNotifications (void) {
    if (!(d->orientationNotifications))
        return;

    d->orientationNotifications = false;
    UIAccelerometer::sharedAccelerometer()->setEnabled(false);
}

void UIDevice::beginGeneratingDeviceOrientationNotifications (void) {
    if (d->orientationNotifications)
        return;

    UIAccelerometer::sharedAccelerometer()->setEnabled(true);
}

bool UIDevice::generatesDeviceOrientationNotifications (void) const {
    return(d->orientationNotifications);
}

// ===========================================================================
//  PUBLIC Properties (Battery Related)
// ===========================================================================
qreal UIDevice::batteryLevel (void) const {
    qreal capacity = -1.0f;
#ifndef UIKIT_SIMULATOR
    QFile file("/sys/devices/platform/bq27000-battery.0/power_supply/bat/capacity");
    if (file.open(QIODevice::ReadOnly)) {
        capacity = qreal(file.readAll().trimmed().toInt()) / 100;
        file.close();
    }
#endif
    return(capacity);
}

QDateTime UIDevice::batteryTimeToFull (void) const {
	QDateTime dateTime;
#ifndef UIKIT_SIMULATOR
	QFile file("/sys/devices/platform/bq27000-battery.0/power_supply/bat/time_to_full_now");
	if (file.open(QIODevice::ReadOnly)) {		
		dateTime = QDateTime::fromTime_t(file.readAll().trimmed().toUInt());
		file.close();
	}
#endif
	return(dateTime);
}

QDateTime UIDevice::batteryTimeToEmpty (void) const {
	QDateTime dateTime;
#ifndef UIKIT_SIMULATOR
	QFile file("/sys/devices/platform/bq27000-battery.0/power_supply/bat/time_to_empty_now");
	if (file.open(QIODevice::ReadOnly)) {		
		dateTime = QDateTime::fromTime_t(file.readAll().trimmed().toUInt());
		file.close();
	}
#endif
	return(dateTime);
}

UIDeviceBatteryState UIDevice::batteryState (void) const {
#ifndef UIKIT_SIMULATOR
    QFile file("/sys/devices/platform/bq27000-battery.0/power_supply/bat/status");
    if (file.open(QIODevice::ReadOnly)) {		
        QString statusString = file.readAll().trimmed().toLower();
        file.close();

        if (statusString == "not charging" || statusString == "discharging")
            return(UIDeviceBatteryStateUnplugged);

        if (statusString == "charging")
            return(UIDeviceBatteryStateCharging);

        if (statusString == "full")
            return(UIDeviceBatteryStateFull);
	}
#endif
	return(UIDeviceBatteryStateUnknown);
}

// ===========================================================================
//  PUBLIC Properties (Leds Related)
// ===========================================================================
#define NEO_LED_RED     "/sys/devices/platform/gta02-led.0/leds/gta02-aux:red/brightness"
#define NEO_LED_BLUE    "/sys/devices/platform/gta02-led.0/leds/gta02-power:blue/brightness"
#define NEO_LED_ORANGE  "/sys/devices/platform/gta02-led.0/leds/gta02-power:orange/brightness"

bool UIDevice::redLedIsEnabled (void) const {
    return(d->ledIsEnabled(NEO_LED_RED));
}

void UIDevice::setRedLedEnabled (bool enabled)  {
    d->setLedEnabled(NEO_LED_RED, enabled);
}

bool UIDevice::blueLedIsEnabled (void) const  {
    return(d->ledIsEnabled(NEO_LED_BLUE));
}

void UIDevice::setBlueLedEnabled (bool enabled)  {
    d->setLedEnabled(NEO_LED_BLUE, enabled);
}

bool UIDevice::orangeLedIsEnabled (void) const {
    return(d->ledIsEnabled(NEO_LED_ORANGE));
}

void UIDevice::setOrangeLedEnabled (bool enabled) {
    d->setLedEnabled(NEO_LED_ORANGE, enabled);
}

// ===========================================================================
//  PUBLIC Slots (Vibrate Related)
// ===========================================================================
void UIDevice::vibrate (qreal force, int time) {
    d->setVibration(quint8(force * 255));

    // Use Time > 0 if you want stop it.
    if (time > 0) QTimer::singleShot(time, this, SLOT(stopVibrate));
}

void UIDevice::stopVibrate (void) {
    d->setVibration(0);
}

// ===========================================================================
//  PUBLIC Slots (System Related)
// ===========================================================================
void UIDevice::systemShutdown (void) {
    QProcess::startDetached("halt");
}

void UIDevice::systemReboot (void) {
    QProcess::startDetached("reboot");
}

// ===========================================================================
//  PUBLIC Slots (Simulator Button Related)
// ===========================================================================
#ifdef UIKIT_SIMULATOR
void UIDevice::pressPowerButton (void) {
    buttonPressed(kUIDeviceKeyCodePower);
}

void UIDevice::pressAuxButton (void) {
    buttonPressed(kUIDeviceKeyCodeAux);
}
#endif

// ===========================================================================
//  PRIVATE Slots
// ===========================================================================
void UIDevice::accelerometerNotification (UIAcceleration *acceleration) {
    if (UIAccelerometer::sharedAccelerometer()->isShaking())
        return;

    UIDeviceOrientation orientation = d->deviceOrientation(acceleration->x(), 
                                                           acceleration->y(), 
                                                           acceleration->z());
    if (orientation != d->orientation &&
        UIDeviceOrientationIsValidInterfaceOrientation(orientation))
    {
        d->orientation = orientation;
        emit orientationChanged(d->orientation);
    }
}

void UIDevice::buttonPressed (int keyCode) {
    UIEvent event;
    if (keyCode == kUIDeviceKeyCodePower) {
        emit powerButtonPressed(&event);
    } else if (keyCode == kUIDeviceKeyCodeAux) {
        emit auxButtonPressed(&event);
    }
}

// ===========================================================================
//  PRIVATE Constructor/Destructor
// ===========================================================================
UIDevice::UIDevice (QObject *parent)
    : QObject(parent), d(new UIDevicePrivate)
{
    qRegisterMetaType<UIDeviceOrientation>("UIDeviceOrientation");

#ifdef Q_WS_QWS
	QWSServer::setBackground(QBrush(Qt::black));
	QWSServer::instance()->setCursorVisible(false);
#endif

    d->readCpuInfo();
    d->readSerialNumber();
    d->calculateUniqueIdentifier();
    d->orientationNotifications = false;
    d->orientation = UIDeviceOrientationPortrait;

#ifndef UIKIT_SIMULATOR
    // Setup Accelerometer Event
    UIAccelerometer *accelerometer = UIAccelerometer::sharedAccelerometer();
    connect(accelerometer, SIGNAL(accelerate(UIAcceleration *)),
            this, SLOT(accelerometerNotification(UIAcceleration *)));

    // Setup Buttons Event
    UIDeviceButtonThread *powerBtn = new UIDeviceButtonThread("/dev/input/event0", 
                                                              kUIDeviceKeyCodePower,
                                                              this);
    connect(powerBtn, SIGNAL(pressed(int)), this, SLOT(buttonPressed(int)));

    UIDeviceButtonThread *auxBtn = new UIDeviceButtonThread("/dev/input/event4", 
                                                            kUIDeviceKeyCodeAux, 
                                                            this);
    connect(auxBtn, SIGNAL(pressed(int)), this, SLOT(buttonPressed(int)));

	// Start Buttons Threads
	QTimer::singleShot(0, powerBtn, SLOT(start()));
	QTimer::singleShot(0, auxBtn, SLOT(start()));
#endif
}

UIDevice::~UIDevice() {
    delete d;
}

